package chances.wechat.data.ibatis;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * 
 * @author xdwang
 * 
 * @ceate 2012-12-19 下午8:01:31
 * 
 * @description 然后就是实现mybatis提供的拦截器接口，编写我们自己的分页实现，原理就是拦截底层JDBC操作相关的Statement对象�?
 *              把前端的分页参数如当前记录索引和每页大小通过拦截器注入到sql语句�?
 *              ，即在sql执行之前通过分页参数重新生成分页sql,而具体的分页sql实现是分离到Dialect接口中去�?
 * 
 * 
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class ,Integer.class}) })
public class PaginationInterceptor implements Interceptor {

    private final static Log log = LogFactory
            .getLog(PaginationInterceptor.class);
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    Dialect.Type databaseType = null;
    Dialect dialect = null;

    public PaginationInterceptor() {
        dialect = new MySQLDialect();
    }

    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation
                .getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        Object parameterObject = boundSql.getParameterObject();
        MetaObject metaStatementHandler = MetaObject.forObject(
                statementHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
        RowBounds rowBounds = (RowBounds) metaStatementHandler
                .getValue("delegate.rowBounds");
        if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
            return invocation.proceed();
        }

        /*
         * Configuration configuration = (Configuration) metaStatementHandler
         * .getValue("delegate.configuration"); try { databaseType =
         * Dialect.Type.valueOf(configuration.getVariables()
         * .getProperty("dialect").toUpperCase()); } catch (Exception e) { //
         * ignore } if (databaseType == null) { throw new RuntimeException(
         * "the value of the dialect property in configuration.xml is not defined : "
         * + configuration.getVariables().getProperty( "dialect")); }
         */

        String originalSql = (String) metaStatementHandler
                .getValue("delegate.boundSql.sql");
        Connection connection = (Connection) invocation.getArgs()[0];
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
                .getValue("delegate.mappedStatement");
        
        long count = getTotalCount(connection,  mappedStatement, boundSql,
                parameterObject);

        int begin = 0;
        if (rowBounds instanceof PageRowBounds) {
            PageRowBounds pager = (PageRowBounds) rowBounds;
            pager.setRecordCount(count - pager.getStart());
            begin = pager.getPageOffset();
        }
        
        //int begin = rowBounds.getOffset();
        String pageSql = dialect.getPageSql(originalSql, begin,
                rowBounds.getLimit());
        metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
        metaStatementHandler.setValue("delegate.rowBounds.offset",
                begin);
        metaStatementHandler.setValue("delegate.rowBounds.limit",
                RowBounds.NO_ROW_LIMIT);

        if (log.isDebugEnabled()) {
            log.debug("生成分页SQL : " + boundSql.getSql());
        }
        
        return invocation.proceed();

    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        String str = properties.getProperty("dialect");
        if (StringUtils.isNotEmpty(str)) {
            this.databaseType = Dialect.Type.valueOf(str.toUpperCase());
            switch (databaseType) {
            case MYSQL:
                dialect = new MySQLDialect();
                break;
            case MSSQL:
                dialect = new SQLServerDialect();
                break;
            case ORACLE:
                dialect = new OracleDialect();
                break;
            default:
                dialect = new MySQLDialect();
            }
        }
    }

    /**
     * get total count
     * 
     * @param sqlSession
     * @param statementName
     * @param values
     * @return
     */
    private long getTotalCount(Connection connection,
            MappedStatement mappedStatement, BoundSql boundSql,
            Object parameterObject) {
        long count = 0l;
        try {
            String sql = dialect.getCountSql(boundSql.getSql());
            PreparedStatement pstmt = connection.prepareStatement(sql);
            DefaultParameterHandler handler = new DefaultParameterHandler(
                    mappedStatement, parameterObject, boundSql);
            handler.setParameters(pstmt);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                count = rs.getLong(1);
            }
            rs.close();
            pstmt.close();
        } catch (Exception e) {
            count = 0l;
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        return count;
    }

}
